package base;
import jm.audio.AudioObject;
import jm.audio.Instrument;
import jm.audio.io.SampleOut;
import jm.audio.synth.*;

/**
 * A basic subtractive synthesizer that allows specification of
 * waveform, low pass filter cutoff, and amplitude envelope.
 *
 * @author Andrew Brown
 */

public final class SubtractiveSynthInst extends Instrument {
    //----------------------------------------------
    // Attributes
    //----------------------------------------------
    private int sampleRate;
    private int filterCutoff;
    private int channels = 2;
    private double[] envValues;
    /**
     * Which waveform to use for the instrument - check the Oscillator class for choices
     */
    private int waveform;
    /**
     * The number of milliseconds in the attack phrase of
     * the amplitude envelope
     */
    private int attack;
    /**
     * The number of milliseconds in the decay phrase of
     * the amplitude envelope
     */
    private int decay;
    /**
     * The level of the sustain phrase of
     * the amplitude envelope, normally between 0.0 and 1.0
     */
    private double sustain;
    /**
     * The number of milliseconds in the release phrase of
     * the amplitude envelope
     */
    private int release;
    /**
     * The number of detuned oscillators to run in parralell
     */
    private int numbOfOsc;
    /**
     * transpose the second oscillator down one octave?
     */
    private boolean subOsc;

    //----------------------------------------------
    // Constructor
    //----------------------------------------------

    /**
     * Basic default constructor to set an initial
     * sampling rate and otherwise uses default values.
     *
     * @param sampleRate
     */
    public SubtractiveSynthInst(int sampleRate) {
        this(sampleRate, Oscillator.SAWTOOTH_WAVE);
    }

    /**
     * Constructor that sets sample rate and waveform,
     * See the jm.audio.synth.Oscillator class for valid waveform values.
     *
     * @param sampleRate The number of samples per second (quality)
     * @param waveform   The oscillator shape to use.
     */
    public SubtractiveSynthInst(int sampleRate, int waveform) {
        this(sampleRate, waveform, 500);
    }

    /**
     * Constructor that sets sample rate, waveform, and low pass filter cutoff.
     *
     * @param sampleRate   The number of samples per second (quality)
     * @param waveform     The oscillator shape to use
     * @param filterCutoff The frequency above which overtones are cut
     */
    public SubtractiveSynthInst(int sampleRate, int waveform, int filterCutoff) {
        this(sampleRate, waveform, filterCutoff, 10, 10, 0.6, 100);
    }

    /**
     * Constructor that sets sample rate, waveform, filter cutoff, and envelope.
     *
     * @param sampleRate   The number of samples per second (quality).
     * @param waveform     The oscillator shape to use
     * @param filterCutoff The frequency above which overtones are cut.
     * @param attack       An envelope value in milliseconds.
     * @param decay        An envelope value in milliseconds.
     * @param sustain      An envelope value for volume, from 0.0 - 1.0.
     * @param release      An envelope value in milliseconds.
     */
    public SubtractiveSynthInst(int sampleRate, int waveform, int filterCutoff,
                                int attack, int decay, double sustain, int release) {
        this(sampleRate, waveform, filterCutoff, attack, decay, sustain, release, 1);
    }

    /**
     * Constructor that sets sample rate, waveform, filter cutoff, and envelope.
     *
     * @param sampleRate   The number of samples per second (quality).
     * @param waveform     The oscillator shape to use
     * @param filterCutoff The frequency above which overtones are cut.
     * @param attack       An envelope value in milliseconds.
     * @param decay        An envelope value in milliseconds.
     * @param sustain      An envelope value for volume, from 0.0 - 1.0.
     * @param release      An envelope value in milliseconds.
     * @param numbOfOsc    The number of parallel oscillators to use.
     */
    public SubtractiveSynthInst(int sampleRate, int waveform, int filterCutoff,
                                int attack, int decay, double sustain, int release, int numbOfOsc) {
        this(sampleRate, waveform, filterCutoff, attack, decay, sustain, release, numbOfOsc, false);
    }

    /**
     * Constructor that sets sample rate, waveform, filter cutoff, and envelope.
     *
     * @param sampleRate   The number of samples per second (quality).
     * @param waveform     The oscillator shape to use
     * @param filterCutoff The frequency above which overtones are cut.
     * @param attack       An envelope value in milliseconds.
     * @param decay        An envelope value in milliseconds.
     * @param sustain      An envelope value for volume, from 0.0 - 1.0.
     * @param release      An envelope value in milliseconds.
     * @param numbOfOsc    The number of parallel oscillators to use.
     * @param subOsc       A choice about lowering the second osc one octave.
     */
    public SubtractiveSynthInst(int sampleRate, int waveform, int filterCutoff,
                                int attack, int decay, double sustain, int release, int numbOfOsc, boolean subOsc) {
        this.sampleRate = sampleRate;
        this.waveform = waveform;
        this.filterCutoff = filterCutoff;
        this.envValues = envValues;
        this.attack = attack;
        this.decay = decay;
        this.sustain = sustain;
        this.release = release;
        this.numbOfOsc = numbOfOsc;
        this.subOsc = subOsc;
    }

    //----------------------------------------------
    // Methods 
    //----------------------------------------------   

    /**
     * Initialisation method used to build the objects that
     * this instrument will use and specify thier interconnections.
     */
    public void createChain() {
        // LFO modulator
        Value modFrequency[] = new Value[numbOfOsc];
        Oscillator modulator[] = new Oscillator[numbOfOsc];
        Oscillator[] oscArray = new Oscillator[numbOfOsc];
        Value constFreq[] = new Value[numbOfOsc];
        Add add[] = new Add[numbOfOsc];
        // volume balance
        Volume[] volArray = new Volume[numbOfOsc];
        // fill
        for (int i = 0; i < numbOfOsc; i++) {
            // modulator
            modFrequency[i] = new Value(this, this.sampleRate, this.channels, (float) (Math.random() * 0.2));
            modulator[i] = new Oscillator(modFrequency[i], Oscillator.SINE_WAVE, Oscillator.FREQUENCY);
            modulator[i].setAmp((float) 0.3);
            // constant
            constFreq[i] = new Value(this, this.sampleRate, this.channels, Value.NOTE_PITCH);
            add[i] = new Add(new AudioObject[]{constFreq[i], modulator[i]});
            // carrier
            oscArray[i] = new Oscillator(add[i], this.waveform, Oscillator.FREQUENCY);
            //oscArray[i] = new Oscillator(this, this.waveform, this.sampleRate, this.channels);
            if (i % 2 == 0 && i > 0) {
                oscArray[i].setFrqRatio((float) (1.0 + 0.001 * (i - 1)));
            } else {
                if (this.subOsc && i == 1) {
                    oscArray[i].setFrqRatio((float) (0.5 - 0.001 * i));
                } else oscArray[i].setFrqRatio((float) (1.0 - 0.001 * i));
            }
            // volume difference between main and other oscillators
            if (i == 0) volArray[i] = new Volume(oscArray[i], 1.0);
            else volArray[i] = new Volume(oscArray[i], 0.3);

        }
        Add add2 = new Add(volArray);
        Filter filt = new Filter(add2, this.filterCutoff, Filter.LOW_PASS);
        ADSR env = new ADSR(filt, attack, decay, sustain, release);
        //Envelope env = new Envelope(filt, new double[] {0.0, 0.0, 0.1, 1.0, 1.0, 0.0});
        Volume vol = new Volume(env);
        StereoPan pan = new StereoPan(vol);
        SampleOut sout = new SampleOut(pan);
    }
}

